﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using System.Threading.Tasks;
using Microsoft.Xna.Framework.Content;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using IronPython.Modules;
using Microsoft.Xna.Framework;
using System.Diagnostics;


namespace CoolEngine.Engine.Scripting
{
    public static class ScriptManager
    {
        public static ScriptEngine Engine { get; private set; }
        private static CompiledCode scopeCode;
        private static string scopeString;
        public static ScriptScope Scope { get; private set; }
        private static ContentManager content;
        public static ScriptRuntime Runtime { get; private set; }
        public static ScriptHost Host { get; private set; }
        private static Dictionary<string, StringBuilder> moduleBuilders = new Dictionary<string, StringBuilder>();
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static ScriptManager()
        {
            //To make sure that IronPython.Modules is included alongside us.
            object dummy = IronPython.Modules.PythonGC.gc;
            Engine = PythonEngine.CurrentEngine;
            Runtime = Engine.Runtime;
            Host = Runtime.Host;

            Scope = Engine.CreateScope(new PythonDictionary());
        }
        public static void SetUpScripts(CoolGame game)
        {
            content = game.Content;
            PublishModules();
        }


        private static void PublishModules()
        {
            LoadAndPublishModule("Script" );
            LoadAndPublishModule("Camera");
            LoadAndPublishModule("Game");
            FinaliseModules();
        }

        private static void FinaliseModules()
        {
            foreach (KeyValuePair<string,StringBuilder> item in moduleBuilders)
            {
                PublishModule(item.Key, item.Value.ToString());
            }
        }

        private static void LoadAndPublishModule(string file)
        {
            PythonSourceFile source = content.Load<PythonSourceFile>("Scripts\\" + file);
            Debug.Assert(!(String.IsNullOrEmpty(source.Module)), "source.Module is null or empty");
            AddToModule(source.Module, source.Text);
            //PublishModule(source.Module, source.Text);
        }

        private static void AddToModule(string module, string text)
        {
            if (!moduleBuilders.ContainsKey(module))
            {
                moduleBuilders.Add(module, new StringBuilder());
            }
            if (moduleBuilders[module].ToString().Contains("import clr"))
            {
                text = text.Replace("import clr", "");
            }
            moduleBuilders[module].AppendLine(text);
        }

        private static void PublishModule(string name, string text)
        {
            //scope.SetVariable("moduleSource", text.Replace(Environment.NewLine, "\n"));
            //scope.SetVariable("moduleName", name);
            //Engine.CreateScriptSourceFromString(CoolEngine.Resources.ImportNewModule, SourceCodeKind.Statements).Execute(scope);
            //    scope.RemoveVariable("moduleSource");
            //scope.RemoveVariable("moduleName");
            ScriptScope ss = Engine.CreateScope();
            Engine.CreateScriptSourceFromString(text, SourceCodeKind.Statements).Execute(ss);

            Scope s = HostingHelpers.GetScope(ss);
            Runtime.Globals.SetVariable(name,s );
        }
        private struct MethodAndPythonMethodAttribute
        {
            public PythonMethodAttribute Attribute;
            public MethodInfo Info;
        }
        public static object RunCommand(string command)
        {
            return Engine.CreateScriptSourceFromString(command, Microsoft.Scripting.SourceCodeKind.Statements).Execute(Scope);
        }
        public static ScriptScope CreateScope()
        {
            ScriptScope scope = Engine.CreateScope();
            scopeCode.Execute(scope);
            return scope;
        }
        public static void SearchForBoundFunctions(IEnumerable<Assembly> assemblies)
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("import clr");
            Runtime.LoadAssembly(typeof(ScriptManager).Assembly);
            Runtime.LoadAssembly(typeof(System.Threading.Tasks.Task).Assembly);
            Runtime.LoadAssembly(typeof(Vector3).Assembly);
            Dictionary<string, List<MethodAndPythonMethodAttribute>> modules = new Dictionary<string, List<MethodAndPythonMethodAttribute>>();
            foreach (Assembly assembly in assemblies.Where(assm => assm.GetCustomAttributes(typeof(ContainsBoundPythonCommands), false).Length > 0))
            {
                bool useAssembly = true;

                foreach (Type type in assembly.GetTypes().Where(type => type.GetCustomAttributes(typeof(ContainsBoundPythonCommands), false).Length > 0))
                {
                    foreach (MethodInfo method in type.GetMethods())
                    {
                        PythonMethodAttribute[] attributes = method.GetCustomAttributes(typeof(PythonMethodAttribute), false).Cast<PythonMethodAttribute>().ToArray();
                        if (attributes.Length == 0)
                        {
                            continue;
                        }
                        if (useAssembly)
                        {
                            AppendAssemblyImport(builder, assembly);
                            useAssembly = false;
                        }
                        foreach (PythonMethodAttribute attribute in attributes)
                        {
                            if (attribute.Module == null)
                            {
                                AppendGlueMethod(builder, method, attribute.Name);
                            }
                            else
                            {
                                if (!modules.ContainsKey(attribute.Module))
                                {
                                    modules.Add(attribute.Module, new List<MethodAndPythonMethodAttribute>());
                                }
                                modules[attribute.Module].Add(new MethodAndPythonMethodAttribute() { Attribute = attribute, Info = method });
                            }
                            
                        }
                    }
                }
            }

            //PublishBoundCommands();
            PublishBoundModules(modules);

            scopeString = builder.ToString();
            //TODO: This has to be moved into FinaliseModules so it gets the loaded scripts as well.
            scopeCode = Engine.CreateScriptSourceFromString(scopeString, Microsoft.Scripting.SourceCodeKind.Statements).Compile();
            scopeCode.Execute(Scope);
        }

        //private static void PublishBoundCommands()
        //{
        //    var commandModules = from pair in CommandParser.Commands
        //                         group pair by pair.Value.Module;

        //    foreach (var pair in commandModules)
        //    {
        //        StringBuilder scriptBuilder = new StringBuilder();
        //        scriptBuilder.AppendLine("import clr");
        //        List<Assembly> assembliesAdded = new List<Assembly>();
        //        foreach (KeyValuePair<string, Command> command in pair)
        //        {
        //            Assembly assembly = command.Value.Target.Method.DeclaringType.Assembly;
        //            if (!assembliesAdded.Contains(assembly))
        //            {
        //                assembliesAdded.Add(assembly);
        //                AppendAssemblyImport(scriptBuilder, assembly);
        //            }
        //            AppendGlueMethod(scriptBuilder, command.Value.Target.Method, command.Key);
        //        }
        //        PublishModule(pair.Key, scriptBuilder.ToString());
        //    }
        //}

        private static void PublishBoundModules(Dictionary<string, List<MethodAndPythonMethodAttribute>> modules)
        {
            foreach (KeyValuePair<string, List<MethodAndPythonMethodAttribute>> item in modules)
            {
                StringBuilder scriptBuilder = new StringBuilder();
                scriptBuilder.AppendLine("import clr");
                List<Assembly> assembliesAdded = new List<Assembly>();
                foreach (MethodAndPythonMethodAttribute maa in item.Value)
                {
                    if (!assembliesAdded.Contains(maa.Info.DeclaringType.Assembly))
                    {
                        assembliesAdded.Add(maa.Info.DeclaringType.Assembly);
                        AppendAssemblyImport(scriptBuilder, maa.Info.DeclaringType.Assembly);
                    }
                    AppendGlueMethod(scriptBuilder, maa.Info, maa.Attribute.Name, maa.Attribute);
                }
                AddToModule(item.Key, scriptBuilder.ToString());
            }
        }

        private static void AppendGlueMethod(StringBuilder builder, MethodInfo method, string name)
        {
            AppendGlueMethod(builder, method, name, null);
        }
        private static void AppendGlueMethod(StringBuilder builder, MethodInfo method, string name, PythonMethodAttribute attribute)
        {
            Type type = method.DeclaringType;
            builder.Append("def ");
            builder.Append(name);
            ParameterInfo[] paramters = method.GetParameters();
            GenerateParameterString(builder, paramters, attribute);
            builder.AppendLine(":");
            builder.Append("    ");
            EmitNamespaceImport(builder, type);
            builder.Append("    ");
            if (method.ReturnType != typeof(void))
            {
                builder.Append("return ");
            }
            builder.Append(type.Name);
            builder.Append('.');
            builder.Append(method.Name);
            GenerateParameterString(builder, paramters, attribute);
            builder.AppendLine("");
        }

        private static void AppendAssemblyImport(StringBuilder builder, Assembly assembly)
        {
            builder.Append("clr.AddReference(\"");
            builder.Append(assembly.GetName().Name);
            builder.AppendLine("\")");
        }

        private static void EmitNamespaceImport(StringBuilder builder, Type type)
        {
            builder.Append("from ");
            builder.Append(type.Namespace);
            builder.Append(" import ");
            builder.AppendLine(type.Name);
        }

        private static void GenerateParameterString(StringBuilder builder, ParameterInfo[] parameters, PythonMethodAttribute attribute)
        {
            builder.Append('(');
            if (attribute != null)
            {
                if (attribute.Parameters == -1)
                {
                    CreateParameterStringFromMethodInfo(builder, parameters);
                }
                else
                {
                    for (int i = 0; i < attribute.Parameters-1; i++)
                    {
                        builder.Append('p');
                        builder.Append(i);
                        builder.Append(',');
                    }
                    builder.Append('p');
                }

            }
            else
            {
                CreateParameterStringFromMethodInfo(builder, parameters);
            }
            builder.Append(')');
        }

        private static void CreateParameterStringFromMethodInfo(StringBuilder builder, ParameterInfo[] parameters)
        {
            for (int i = 0; i < parameters.Length - 1; i++)
            {
                builder.Append(parameters[i].Name);
                builder.Append(", ");
            }
            if (parameters.Length > 0)
            {
                builder.Append(parameters[parameters.Length - 1].Name);
            }
            
        }
        public static void SearchForBoundFunctions()
        {
            SearchForBoundFunctions(AppDomain.CurrentDomain.GetAssemblies());
        }
    }
}